﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Linq;

using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;

using RexToy;
using RexToy.Xml;

namespace ScriptXCompiler
{
    public class CompileEngine
    {
        private string _startPath;
        private string _outputScript;
        private bool _singleFile;
        private bool _writeFile;
        private string _outputDir;
        private string _config;
        private bool _jsonNameAddQuote;
        private bool _watermark;

        private ErrorAggregator _aggregator;

        private List<string> _refPathList = new List<string>();
        private List<string> _compilePathList = new List<string>();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="csprojPath"></param>
        /// <param name="outputDir"></param>
        /// <param name="config"></param>
        /// <param name="singleFile"></param>
        /// <param name="writeFile"></param>
        /// <param name="jsonAddNameQuote"></param>
        /// <param name="watermark">only use for test</param>
        public void InitializeCompiler(string csprojPath, string outputDir, string config, bool singleFile, bool writeFile, bool jsonAddNameQuote, string supressWarnings, bool watermark = true)
        {
            string[] numbers = supressWarnings.Split(',', StringSplitOptions.RemoveEmptyEntries);
            var q = from n in numbers
                    select int.Parse(n);

            _aggregator = new ErrorAggregator(q);

            _outputDir = outputDir;
            _singleFile = singleFile;
            _writeFile = writeFile;
            _config = config;
            _jsonNameAddQuote = jsonAddNameQuote;
            _watermark = watermark;

            _startPath = Path.GetDirectoryName(csprojPath);
            XDoc doc = XDoc.LoadFromFile(csprojPath);
            doc.AddNamespace("x", "http://schemas.microsoft.com/developer/msbuild/2003");

            _outputScript = doc.GetStringValue(@"x:PropertyGroup/x:AssemblyName") + ".js";

            //Note:Project ref, get the assembly name and should be able to found under output(bin/debug)
            var rl = doc.NavigateToList(@"x:ItemGroup/x:ProjectReference");
            foreach (var r in rl)
            {
                var refPrjPath = r.GetStringValue(@"@Include");
                XDoc xref = XDoc.LoadFromFile(Path.Combine(_startPath, refPrjPath));
                xref.AddNamespace("x", "http://schemas.microsoft.com/developer/msbuild/2003");
                var libName = xref.GetStringValue(@"x:PropertyGroup/x:AssemblyName");

                string assemblyPath = Path.GetFullPath(Path.Combine(outputDir, libName + ".dll"));

                _refPathList.Add(assemblyPath);
                CheckAssembly(assemblyPath);
            }

            rl = doc.NavigateToList(@"x:ItemGroup/x:Reference");
            foreach (var r in rl)
            {
                string hint = r.GetStringValue(@"x:HintPath");
                if (string.IsNullOrEmpty(hint))
                {
                    var issue = CompileIssue.CreateNoLocationIssue(IssueType.Error, IssueId.InvalidAssembly, r.GetStringValue("@Include"));
                    _aggregator.AppendIssue(issue);
                }
                else
                {
                    string assemblyPath = Path.GetFullPath(Path.Combine(_startPath, hint));
                    _refPathList.Add(assemblyPath);
                    CheckAssembly(assemblyPath);
                }
            }

            //Note:Item to be compiled.
            var cl = doc.NavigateToList(@"x:ItemGroup/x:Compile");
            foreach (var c in cl)
            {
                string path = c.GetStringValue("@Include");
                if (path == @"Properties\AssemblyInfo.cs")
                    continue;
                _compilePathList.Add(Path.Combine(_startPath, path));
            }
        }

        //Extend: customized Object is not compatible, check is not implemented yet.
        private void CheckAssembly(string path)
        {
            //Assembly assembly = Assembly.LoadFrom(path);

            //if (assembly.FullName == "mscorlib, Version=0.6.0.0, Culture=neutral, PublicKeyToken=null")
            //{
            //    return;
            //}

            //try
            //{
            //    Console.WriteLine("Loading assembly [{0}]", path);
            //    var m = assembly.ManifestModule;
            //    CompileIssue issue = new CompileIssue(IssueLocation.NA, IssueType.Error, IssueId.InvalidAssembly, path);
            //    _aggregator.AppendIssue(issue);
            //}
            //catch (TypeLoadException ex)
            //{
            //    if ("System.ScriptAssemblyAttribute" == ex.TypeName)
            //    {
            //        //Note:it's correct
            //        return;
            //    }

            //    throw;
            //}
        }

        public ErrorAggregator Compile()
        {
            try
            {
                List<SyntaxTree> treeList = new List<SyntaxTree>();
                foreach (var f in _compilePathList)
                {
                    SyntaxTree sTree = SyntaxTree.ParseFile(f);
                    treeList.Add(sTree);
                }

                List<MetadataReference> references = new List<MetadataReference>();
                foreach (string dir in _refPathList)
                {
                    var reference = new MetadataFileReference(dir);
                    references.Add(reference);
                }

                var option = new CompilationOptions(OutputKind.DynamicallyLinkedLibrary);
                var c = Compilation.Create("CSXC", option, treeList, references);
                CodeTemplate template = CodeTemplate.Parse(Path.Combine(_startPath, @"Properties\template.xml"));

                TranslateDictionary dictionary = new TranslateDictionary();
                dictionary.Load(Path.Combine(_startPath, @"Properties\translate.xml"));

                var trees = OrderResolver.Sort(c, _aggregator);

                if (_singleFile)
                {
                    WriteToSingleFile(trees, c, template, dictionary);
                }
                else
                {
                    WriteToMultipleFile(trees, c, template, dictionary);
                }

                return _aggregator;
            }
            catch (Exception e)
            {
                CompileIssue issue = CompileIssue.CreateNoLocationIssue(IssueType.Error, IssueId.InternalError, e.Message);
                _aggregator.AppendIssue(issue);
                return _aggregator;
            }
        }

        private void WriteToSingleFile(IList<SyntaxTree> trees, Compilation c, CodeTemplate template, TranslateDictionary dictionary)
        {
            File.Delete(Path.Combine(_outputDir, _outputScript));

            IOutput output;
            if (_writeFile)
                output = new FileOutput(Path.Combine(_outputDir, _outputScript), true);
            else
                output = new ConsoleOutput();

            using (output)
            {
                foreach (SyntaxTree sourceTree in trees)
                {
                    SemanticModel model = c.GetSemanticModel(sourceTree);
                    Translator t = new Translator(model, template, dictionary, output, _aggregator, _jsonNameAddQuote);
                    SyntaxNode newSource = t.Visit(sourceTree.GetRoot());
                }
                if (_config.ToUpper() == "RELEASE")
                {
                    output.Compress();
                }
                if (_watermark)
                    output.WriteLine("//# This file is compiled by CSXC {0} from C#.", Program.GetVersion());
                output.Flush();
            }
        }


        private void WriteToMultipleFile(IList<SyntaxTree> trees, Compilation c, CodeTemplate template, TranslateDictionary dictionary)
        {
            foreach (SyntaxTree sourceTree in trees)
            {
                _outputScript = Path.GetFileNameWithoutExtension(sourceTree.FilePath) + ".js";
                string relPath = Path.GetDirectoryName(sourceTree.FilePath.RemoveBegin(_startPath)).RemoveBegin("\\");

                string outDir = Path.Combine(_outputDir, relPath);
                if (!Directory.Exists(outDir))
                    Directory.CreateDirectory(outDir);
                _outputScript = Path.Combine(relPath, _outputScript);

                IOutput output;
                if (_writeFile)
                    output = new FileOutput(Path.Combine(_outputDir, _outputScript), false);
                else
                    output = new ConsoleOutput();

                using (output)
                {
                    SemanticModel model = c.GetSemanticModel(sourceTree);
                    Translator t = new Translator(model, template, dictionary, output, _aggregator, _jsonNameAddQuote);
                    SyntaxNode newSource = t.Visit(sourceTree.GetRoot());

                    if (_config.ToUpper() == "RELEASE")
                    {
                        output.Compress();
                    }
                    if (_watermark)
                        output.WriteLine("//# This file is compiled by CSXC {0} from C#.", Program.GetVersion());
                    output.Flush();
                }
            }
        }
    }
}
